﻿/* ######################################## *\
 * ### Copyright (C) 2009 AJ Ravindiran ### *
\* ######################################## */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AJRavindiran.Jolt.RuneScape.Models.Items.Container
{
    /// <summary>
    /// Represents a container for items.
    /// </summary>
    public class ItemContainer
    {
        #region Properties
        /// <summary>
        /// The ammount of items this container can initially hold.
        /// </summary>
        private int capacity;
        /// <summary>
        /// The container for items.
        /// </summary>
        private Item[] items;
        /// <summary>
        /// The type this container is. <see cref="ItemContainerType.cs"/>
        /// </summary>
        private ItemContainerType type;
        #endregion Properties

        #region Properties
        /// <summary>
        /// Gets the container's initial capacity.
        /// </summary>
        public int Capacity
        {
            get { return this.capacity; }
        }

        /// <summary>
        /// Gets the length of the items container.
        /// </summary>
        public int Length
        {
            get {  return this.items.Length; }
        }

        /// <summary>
        /// Gets the current ammount of items in the container.
        /// </summary>
        public int Size
        {
            get
            {
                int size = 0;
                for (int i = 0; i < items.Length; i++)
                    if (items[i] != null)
                        size++;
                return size;
            }
        }

        /// <summary>
        /// Gets the container's type.
        /// </summary>
        public ItemContainerType Type
        {
            get { return this.type; }
        }
        #endregion Properties

        #region Constructors
        /// <summary>
        /// Constructs a new item container.
        /// </summary>
        /// <param name="type">What <code>ItemContainerType</code> the contaier will be.</param>
        /// <param name="capacity">Item capacity.</param>
        public ItemContainer(ItemContainerType type, int capacity)
        {
            this.type = type;
            this.capacity = capacity;
            this.items = new Item[capacity];
        }
        #endregion Constructors

        #region Methods
        /// <summary>
        /// Shifts all items to the top left of the container leaving no gaps.
        /// </summary>
        public void Shift()
        {
            Item[] oldItems = items;
            items = new Item[capacity];
            int newIndex = 0;
            for (int i = 0; i < items.Length; i++)
            {
                if (oldItems[i] != null)
                {
                    items[newIndex] = oldItems[i];
                    newIndex++;
                }
            }
        }

        /// <summary>
        /// Gets an item instance.
        /// </summary>
        /// <param name="slotId">The slot id of the item.</param>
        /// <returns>Returns a item instance from the given item slot id.</returns>
        public Item Get(int slotId)
        {
            return items[slotId];
        }

        /// <summary>
        /// Get an item instance by it's item id.
        /// </summary>
        /// <param name="itemId">The id of the item to return.</param>
        /// <returns>Returns the first item with the matching id.</returns>
        public Item GetById(int itemId)
        {
            for (int i = 0; i < items.Length; i++)
            {
                if (items[i].Id == itemId)
                    return items[i];
            }
            return null;
        }

        /// <summary>
        /// Gets the slot id of a given item.
        /// </summary>
        /// <param name="itemId">The item's id.</param>
        /// <returns>Returns the slot id where the item is located.</returns>
        public int GetSlotById(int itemId)
        {
            for (int i = 0; i < items.Length; i++)
            {
                if (items[i] == null)
                    continue;
                if (items[i].Id == itemId)
                    return i;
            }
            return -1;
        }

        /// <summary>
        /// Sets a item instance.
        /// </summary>
        /// <param name="slotId">The slot id of the item.</param>
        /// <param name="item">The instance which will replace the older one.</param>
        public void Set(int slotId, Item item)
        {
            items[slotId] = item;
        }

        /// <summary>
        /// Gets an availible slot from the item container.
        /// </summary>
        /// <returns>Returns an availible slot id. If there 
        /// are not slots availible, it will return -1.</returns>
        public int GetAvailibleSlot()
        {
            for (int i = 0; i < items.Length; i++)
                if (items[i] == null)
                    return i;
            return -1;
        }

        /// <summary>
        /// Gets the availible slots.
        /// </summary>
        /// <returns>Returns the number of slots availible.</returns>
        public int GetAvailibleSlots()
        {
            return this.capacity - Size;
        }

        /// <summary>
        /// Add an item to the container.
        /// </summary>
        /// <param name="item">The item to add.</param>
        /// <returns>Returns true if the procedure has succeeded.</returns>
        public bool Add(Item item)
        {
            // If the item is stackable, noted or is a bank item, we will stack it into that one slot.
            if (Type.Equals(ItemContainerType.AlwaysStack) || item.GetDefinition().Stackable || item.GetDefinition().Noted)
            {
                for (int i = 0; i < items.Length; i++)
                {
                    if (items[i] != null && items[i].Id == item.Id)
                    {
                        Set(i, new Item(items[i].Id, items[i].Amount + item.Amount));
                        return true;
                    }
                }
            }
            // If not, the item is a single item, and now check if the item contains more than one.
            else
            {
                if (item.Amount > 1)
                {
                    if (GetAvailibleSlots() >= item.Amount)
                    {
                        for (int i = 0; i < item.Amount; i++)
                        {
                            Set(GetAvailibleSlot(), new Item(item.Id, 1));
                        }
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }

            int index = GetAvailibleSlot();
            if (index == -1)
            {
                return false;
            }
            else
            {
                Set(index, item);
                return true;
            }
        }

        /// <summary>
        /// Remove an item from the container.
        /// </summary>
        /// <param name="item">The item we want to remove.</param>
        public int Remove(Item item)
        {
            return Remove(item, -1);
        }

        /// <summary>
        /// Remove an item from the container via a preferred slot.
        /// </summary>
        /// <param name="item">The item to remove.</param>
        /// <param name="preferredSlot">The slot to remove from.</param>
        public int Remove(Item item, int preferredSlot)
        {
            int removed = 0;
            if (Type.Equals(ItemContainerType.AlwaysStack) || item.GetDefinition().Stackable || item.GetDefinition().Noted)
            {
                int slot = GetSlotById(item.Id);
                Item stack = Get(slot);
                if (stack.Amount > item.Amount)
                {
                    removed = item.Amount;
                    Set(slot, new Item(stack.Id, stack.Amount - item.Amount));
                }
                else
                {
                    removed = stack.Amount;
                    Set(slot, null);
                }
            }
            else
            {
                for (int i = 0; i < item.Amount; i++)
                {
                    int slot = GetSlotById(item.Id);
                    if (i == 0 && preferredSlot != -1)
                    {
                        Item inSlot = Get(preferredSlot);
                        if (inSlot.Id == item.Id)
                        {
                            slot = preferredSlot;
                        }
                    }
                    if (slot != -1)
                    {
                        removed++;
                        Set(slot, null);
                    }
                    else
                    {
                        break;
                    }
                }
            }
            return removed;
        }

        /// <summary>
        /// Remove all items of the same id.
        /// </summary>
        /// <param name="item">The item to remove.</param>
        public void RemoveAll(Item item)
        {
            for (int i = 0; i < items.Length; i++)
            {
                if (items[i] != null)
                {
                    if (items[i].Id == item.Id)
                    {
                        Set(i, null);
                    }
                }
            }
        }

        /// <summary>
        /// Check if the container contains a specific item.
        /// </summary>
        /// <param name="item">Item to check.</param>
        /// <returns>Returns true if the item exists.</returns>
        public bool ContainsOne(Item item)
        {
            for (int i = 0; i < items.Length; i++)
            {
                if (items[i] != null)
                {
                    if (items[i].Id == item.Id)
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        /// <summary>
        /// Check if the container contains a specific item.
        /// </summary>
        /// <param name="item">Item to check.</param>
        /// <returns>Returns true if the item exists.</returns>
        public bool Contains(Item item)
        {
            int amtOf = 0;
            for (int i = 0; i < items.Length; i++)
            {
                if (items[i] != null)
                {
                    if (items[i].Id == item.Id)
                    {
                        amtOf += items[i].Amount;
                    }
                }
            }
            return amtOf >= item.Amount;
        }

        /// <summary>
        /// Gets the number of items of the same id.
        /// </summary>
        /// <param name="item">The item to find.</param>
        /// <returns>Returns the number of items found with the same id.</returns>
        public int GetAmount(Item item)
        {
            int count = 0;
            for (int i = 0; i < items.Length; i++)
            {
                if (items[i] != null)
                {
                    if (items[i].Id == item.Id)
                    {
                        count += items[i].Amount;
                    }
                }
            }
            return count;
        }

        /// <summary>
        /// Gets the number of items of the same id.
        /// </summary>
        /// <param name="itemId"></param>
        /// <returns></returns>
        public int GetAmmount(int itemId)
        {
            int count = 0;
            for (int i = 0; i < items.Length; i++)
            {
                if (items[i] != null)
                {
                    if (items[i].Id == itemId)
                    {
                        count += items[i].Amount;
                    }
                }
            }
            return count;
        }

        /// <summary>
        /// Check if the container contains a specific item.
        /// </summary>
        /// <param name="itemId">The id of the item to look for.</param>
        /// <returns>Returns true if the item exists.</returns>
        public bool Contains(int itemId)
        {
            return GetSlotById(itemId) != -1;
        }

        /// <summary>
        /// Clears the container.
        /// </summary>
        public void Clear()
        {
            items = new Item[capacity];
        }

        /// <summary>
        /// Returns the container as an array.
        /// </summary>
        /// <returns></returns>
        public Item[] ToArray()
        {
            return this.items;
        }

        /// <summary>
        /// Copy the container to an already existing array.
        /// </summary>
        /// <param name="array">The array the copy the items to.</param>
        public void CopyTo(Array array)
        {
            array = items;
        }

        /// <summary>
        /// Copy the container to an already existing container.
        /// </summary>
        /// <param name="container">The container to copy to.</param>
        public void CopyTo(ItemContainer container)
        {
            container = this;
        }

        /// <summary>
        /// Determines if the container has enough space for a new item.
        /// </summary>
        /// <param name="item">The item to determine.</param>
        /// <returns>Returns true if the container has enough space.</returns>
        public bool HasSpace(Item item)
        {
            if (Type.Equals(ItemContainerType.AlwaysStack) || item.GetDefinition().Stackable || item.GetDefinition().Noted)
            {
                for (int i = 0; i < items.Length; i++)
                {
                    if (items[i] != null)
                    {
                        if (items[i].Id == item.Id)
                        {
                            return true;
                        }
                    }
                }
            }
            else
            {
                if (item.Amount > 1)
                {
                    if (GetAvailibleSlots() >= item.Amount)
                        return true;
                    else
                        return false;
                }
            }
            int index = GetAvailibleSlot();
            if (index == -1)
            {
                return false;
            }
            return true;
        }
        #endregion Methods
    }
}
